Explore o Temporal Duration do JavaScript, a API para aritmética precisa de intervalos de tempo. Gerencie durações de forma global, evitando as armadilhas do objeto Date.
JavaScript Temporal Duration: Dominando a Aritmética e Formatação de Intervalos de Tempo para Aplicações Globais
Gerenciar o tempo no desenvolvimento de software é notoriamente complexo. Desde o acompanhamento de cronogramas de projetos em diferentes continentes até o agendamento de videoconferências internacionais, as nuances de intervalos de tempo, fusos horários e horário de verão podem rapidamente levar a bugs sutis, mas críticos. Por décadas, os desenvolvedores JavaScript lutaram com o objeto Date nativo, uma ferramenta que, embora funcional para pontos simples de data e hora, fica aquém quando se trata de aritmética precisa de intervalos de tempo e gerenciamento de tempo robusto e globalmente consciente.
Apresentamos a API Temporal do JavaScript – uma proposta inovadora projetada para fornecer uma API moderna, robusta e amigável para trabalhar com datas e horas em JavaScript. Entre seus novos e poderosos tipos, o Temporal.Duration se destaca como a solução definitiva para lidar com intervalos de tempo. Este artigo fará um mergulho profundo no Temporal.Duration, explorando suas capacidades de aritmética, comparação e formatação inteligente, garantindo que suas aplicações possam gerenciar o tempo com precisão e clareza globais.
Seja construindo um sistema de logística global, uma plataforma de negociação financeira ou um planejador de eventos multizona, entender o Temporal.Duration é crucial para eliminar ambiguidades relacionadas ao tempo e oferecer experiências de usuário confiáveis e internacionalizadas.
As Insuficiências do Objeto Date do JavaScript para Intervalos de Tempo
Antes de celebrarmos a chegada do Temporal.Duration, é essencial entender as limitações do objeto Date existente, especialmente ao lidar com intervalos de tempo. O objeto Date representa um ponto específico no tempo, medido em milissegundos desde a época Unix (1º de janeiro de 1970, UTC). Embora possa ser usado para realizar aritmética básica, ele carrega várias falhas inerentes que o tornam inadequado para um gerenciamento robusto de durações:
-
Mutabilidade: Objetos
Datesão mutáveis. Qualquer operação em um objetoDatealtera seu estado interno, o que pode levar a efeitos colaterais inesperados e bugs difíceis de rastrear, particularmente em aplicações complexas ou ambientes concorrentes.const d = new Date('2023-01-15T10:00:00Z'); const d2 = d; // d2 agora referencia o mesmo objeto que d d.setHours(d.getHours() + 1); console.log(d.toISOString()); // 2023-01-15T11:00:00.000Z console.log(d2.toISOString()); // 2023-01-15T11:00:00.000Z (d2 também mudou!) -
Falta de um Conceito de Duração: O objeto
Datenão tem um conceito direto de "duração" ou "período". O cálculo da diferença entre duas datas resulta em um número de milissegundos, que precisa ser convertido manualmente em anos, meses, dias, etc. Essa conversão manual é propensa a erros, especialmente ao lidar com meses de duração variável ou anos bissextos.const date1 = new Date('2023-01-01T00:00:00Z'); const date2 = new Date('2023-03-01T00:00:00Z'); const diffMs = date2.getTime() - date1.getTime(); // Quantos meses são isso? E quanto aos anos bissextos? // (diffMs / (1000 * 60 * 60 * 24 * 30)) é, na melhor das hipóteses, uma aproximação. console.log(`Diferença em milissegundos: ${diffMs}`); console.log(`Dias aproximados: ${diffMs / (1000 * 60 * 60 * 24)}`); // Funciona para dias, mas não é robusto para meses/anos -
Ambiguidade de Fuso Horário: Objetos
Datefrequentemente confundem a hora local com o UTC. Embora armazenem internamente milissegundos UTC, seus métodos frequentemente operam no fuso horário local do sistema por padrão, levando a confusão e inconsistências ao trabalhar com sistemas distribuídos ou usuários internacionais. - Desafios do Horário de Verão (DST): As transições de DST podem fazer com que os dias tenham 23 ou 25 horas de duração. A aritmética simples (por exemplo, adicionar 24 horas a uma data) pode nem sempre resultar no próximo dia do calendário, levando a cálculos incorretos quando se assume que um "dia" é um período fixo de 24 horas.
Essas limitações historicamente forçaram os desenvolvedores a depender de bibliotecas de terceiros como Moment.js ou date-fns, ou a escrever código personalizado complexo e propenso a erros para lidar corretamente com intervalos de tempo. O Temporal visa trazer essas capacidades nativamente para o JavaScript.
Apresentando o JavaScript Temporal: Uma Abordagem Moderna para o Tempo
A API Temporal é um novo objeto global e abrangente no JavaScript, projetado para ser um substituto moderno para o antigo objeto Date. Seus princípios fundamentais são imutabilidade, manipulação explícita de fuso horário e uma clara separação de responsabilidades entre diferentes conceitos de tempo. O Temporal introduz várias novas classes, cada uma representando um aspecto distinto do tempo:
Temporal.Instant: Um ponto específico e inequívoco no tempo, independente de qualquer calendário ou fuso horário (semelhante a um timestamp Unix, mas com precisão de nanossegundo).Temporal.ZonedDateTime: Um ponto específico no tempo em um calendário e fuso horário específicos. Esta é a representação mais completa de uma data e hora específicas para um usuário.Temporal.PlainDate: Uma data de calendário (ano, mês, dia) sem hora ou fuso horário.Temporal.PlainTime: Uma hora de relógio (hora, minuto, segundo, etc.) sem data ou fuso horário.Temporal.PlainDateTime: Uma data de calendário e hora de relógio juntas, sem fuso horário.Temporal.PlainYearMonth: Um ano e mês específicos em um sistema de calendário.Temporal.PlainMonthDay: Um mês e dia específicos em um sistema de calendário.Temporal.Duration: Uma duração de tempo com sinal, como "5 horas e 30 minutos" ou "2 dias". Este é o nosso foco para este guia.
Todos os objetos Temporal são imutáveis, o que significa que operações como adicionar ou subtrair tempo criam novos objetos em vez de modificar os existentes, aumentando a previsibilidade e reduzindo bugs.
Entendendo o Temporal.Duration
Um Temporal.Duration representa uma duração de tempo. Crucialmente, ele é independente de um ponto de início ou fim específico. É simplesmente "quanto tempo" passou ou passará. Pode ser composto por anos, meses, semanas, dias, horas, minutos, segundos, milissegundos, microssegundos e nanossegundos. Cada componente é um número inteiro e pode ser positivo ou negativo.
Por exemplo, "2 horas e 30 minutos" é uma duração. "O período de 1º de janeiro a 1º de março" é uma duração entre dois pontos específicos, que pode ser *representada* por um Temporal.Duration, mas a Duração em si é apenas o intervalo.
Criando uma Duração
Existem várias maneiras diretas de criar objetos Temporal.Duration:
1. Usando o Construtor
O construtor permite especificar cada componente diretamente. Note que os argumentos são ordenados da maior unidade (anos) para a menor (nanossegundos).
// new Temporal.Duration(anos, meses, semanas, dias, horas, minutos, segundos, milissegundos, microssegundos, nanossegundos)
// Uma duração de 2 horas e 30 minutos
const duration1 = new Temporal.Duration(0, 0, 0, 0, 2, 30, 0, 0, 0, 0);
console.log(duration1.toString()); // P2H30M
// Uma duração de 1 ano, 2 meses, 3 dias
const duration2 = new Temporal.Duration(1, 2, 0, 3);
console.log(duration2.toString()); // P1Y2M3D
// Uma duração de -5 dias
const duration3 = new Temporal.Duration(0, 0, 0, -5);
console.log(duration3.toString()); // P-5D
2. Usando Temporal.Duration.from() com um Objeto
Esta é frequentemente a maneira mais legível de criar durações, permitindo que você especifique apenas os componentes de que precisa.
// Duração de 1,5 horas
const halfHourDuration = Temporal.Duration.from({ hours: 1, minutes: 30 });
console.log(halfHourDuration.toString()); // P1H30M
// Duração de 7 dias (1 semana)
const oneWeekDuration = Temporal.Duration.from({ days: 7 });
console.log(oneWeekDuration.toString()); // P7D
// Duração com segundos fracionários (ex: 2,5 segundos)
const twoPointFiveSeconds = Temporal.Duration.from({ seconds: 2, milliseconds: 500 });
console.log(twoPointFiveSeconds.toString()); // PT2.5S
// Duração negativa
const negativeDuration = Temporal.Duration.from({ minutes: -45 });
console.log(negativeDuration.toString()); // PT-45M
3. Usando Temporal.Duration.from() com uma String ISO 8601
O Temporal utiliza o formato de duração ISO 8601, que é um padrão para representar durações. Isso é excelente para analisar durações de fontes de dados externas.
O formato geralmente se parece com P[anos]Y[meses]M[semanas]W[dias]DT[horas]H[minutos]M[segundos]S. O T separa os componentes de data dos componentes de tempo.
// 1 ano, 2 meses, 3 dias
const isoDuration1 = Temporal.Duration.from('P1Y2M3D');
console.log(isoDuration1.toString()); // P1Y2M3D
// 4 horas, 5 minutos, 6 segundos
const isoDuration2 = Temporal.Duration.from('PT4H5M6S');
console.log(isoDuration2.toString()); // PT4H5M6S
// Uma duração combinada
const isoDuration3 = Temporal.Duration.from('P7DT12H30M');
console.log(isoDuration3.toString()); // P7DT12H30M
// Segundos fracionários também são suportados
const isoDuration4 = Temporal.Duration.from('PT1.5S');
console.log(isoDuration4.toString()); // PT1.5S
Realizando Aritmética com Durações
O verdadeiro poder do Temporal.Duration brilha em suas capacidades aritméticas. Você pode adicionar, subtrair, multiplicar e dividir durações, e também adicioná-las/subtraí-las de outros tipos de data e hora do Temporal. Todas as operações retornam novos objetos Temporal.Duration devido à imutabilidade.
Adicionando Durações
O método add() combina duas durações.
const sprintDuration = Temporal.Duration.from({ weeks: 2 });
const bufferDuration = Temporal.Duration.from({ days: 3 });
const totalProjectTime = sprintDuration.add(bufferDuration);
console.log(totalProjectTime.toString()); // P2W3D (ou P17D se normalizado mais tarde)
// Adicionar uma duração negativa é equivalente a subtrair
const result = Temporal.Duration.from({ hours: 5 }).add({ hours: -2 });
console.log(result.toString()); // PT3H
Você também pode adicionar uma duração a qualquer objeto de data/hora do Temporal. É aqui que a mágica acontece, pois o Temporal lida corretamente com mudanças de fuso horário e transições de DST quando relevante.
const projectStart = Temporal.PlainDateTime.from('2023-10-26T09:00:00');
const projectDuration = Temporal.Duration.from({ days: 10, hours: 4 });
const projectEnd = projectStart.add(projectDuration);
console.log(projectEnd.toString()); // 2023-11-05T13:00:00
// Com um ZonedDateTime, as regras de fuso horário são aplicadas corretamente
const meetingStartUTC = Temporal.ZonedDateTime.from('2024-03-09T14:00:00[UTC]');
const meetingDuration = Temporal.Duration.from({ hours: 1, minutes: 45 });
const meetingEndUTC = meetingStartUTC.add(meetingDuration);
console.log(meetingEndUTC.toString()); // 2024-03-09T15:45:00+00:00[UTC]
// Exemplo cruzando um limite de DST (assumindo que 'Europe/Berlin' muda às 03:00 em 2024-03-31)
const springForwardStart = Temporal.ZonedDateTime.from('2024-03-30T22:00:00[Europe/Berlin]');
const twentyFourHours = Temporal.Duration.from({ hours: 24 });
const nextDay = springForwardStart.add(twentyFourHours); // Adiciona 24 horas reais
console.log(springForwardStart.toString()); // 2024-03-30T22:00:00+01:00[Europe/Berlin]
console.log(nextDay.toString()); // 2024-03-31T23:00:00+02:00[Europe/Berlin] (A hora local pulou uma hora)
Note como adicionar 24 horas a 2024-03-30T22:00:00 em Berlim (que é UTC+1) resulta em 2024-03-31T23:00:00 (agora UTC+2). O relógio avançou uma hora, então a hora do relógio de parede é uma hora mais tarde na mesma data em relação à hora de início do relógio de parede. Isso demonstra precisamente a consciência do Temporal sobre fuso horário e DST ao realizar aritmética em `ZonedDateTime`.
Subtraindo Durações
O método subtract() funciona de forma semelhante a add(), mas remove tempo.
const deadlineDuration = Temporal.Duration.from({ days: 30 });
const gracePeriod = Temporal.Duration.from({ days: 5 });
const effectiveDeadline = deadlineDuration.subtract(gracePeriod);
console.log(effectiveDeadline.toString()); // P25D
const taskEnd = Temporal.PlainDateTime.from('2023-12-01T17:00:00');
const taskDuration = Temporal.Duration.from({ hours: 8, minutes: 30 });
const taskStart = taskEnd.subtract(taskDuration);
console.log(taskStart.toString()); // 2023-12-01T08:30:00
Multiplicando e Dividindo Durações
Os métodos multiply() e divide() escalam os componentes de uma duração por um determinado fator. Isso é útil para cenários como calcular o tempo total para múltiplas iterações de uma tarefa.
const trainingSession = Temporal.Duration.from({ minutes: 45 });
const weeklyTraining = trainingSession.multiply(5); // Cinco sessões por semana
console.log(weeklyTraining.toString()); // PT225M
const totalProjectHours = Temporal.Duration.from({ hours: 160 });
const teamMembers = 4;
const hoursPerMember = totalProjectHours.divide(teamMembers);
console.log(hoursPerMember.toString()); // PT40H
Negando Durações
O método negate() inverte o sinal de todos os componentes de uma duração. Uma duração positiva se torna negativa, e vice-versa.
const delayDuration = Temporal.Duration.from({ hours: 3 });
const advanceDuration = delayDuration.negate();
console.log(delayDuration.toString()); // PT3H
console.log(advanceDuration.toString()); // PT-3H
Valor Absoluto de Durações
O método abs() retorna um novo Temporal.Duration com todos os componentes tornados positivos, efetivamente lhe dando a magnitude da duração, independentemente de seu sinal.
const negativeDelay = Temporal.Duration.from({ minutes: -60 });
const positiveDuration = negativeDelay.abs();
console.log(negativeDelay.toString()); // PT-60M
console.log(positiveDuration.toString()); // PT60M
Comparando e Normalizando Durações
Comparar durações pode ser complicado, especialmente quando unidades diferentes estão envolvidas (ex: 1 mês é igual a 30 dias?). O Temporal fornece ferramentas tanto para comparação quanto para normalização para lidar com essas complexidades.
Comparando Durações com compare()
O método estático Temporal.Duration.compare(duration1, duration2, options) retorna:
-1seduration1for menor queduration20seduration1for igual aduration21seduration1for maior queduration2
Crucialmente, ao comparar durações que incluem unidades de comprimento variável como anos, meses ou semanas, você frequentemente precisa fornecer uma opção relativeTo. Este parâmetro é um objeto `Temporal.ZonedDateTime` ou `Temporal.PlainDateTime` que fornece contexto sobre como interpretar essas unidades (ex: quantos dias há em um mês ou ano específico).
const oneHour = Temporal.Duration.from({ hours: 1 });
const sixtyMinutes = Temporal.Duration.from({ minutes: 60 });
console.log(Temporal.Duration.compare(oneHour, sixtyMinutes)); // 0 (Eles são equivalentes)
const oneMonth = Temporal.Duration.from({ months: 1 });
const thirtyDays = Temporal.Duration.from({ days: 30 });
// Sem relativeTo, comparações de mês/ano são difíceis
console.log(Temporal.Duration.compare(oneMonth, thirtyDays)); // 0 (O Temporal faz uma suposição razoável sem contexto, geralmente baseada na média)
// Com relativeTo, a comparação é precisa com base no calendário do contexto
const startOfJanuary = Temporal.PlainDate.from('2023-01-01');
const endOfFebruaryLeap = Temporal.PlainDate.from('2024-02-01'); // Ano bissexto
// Em janeiro de 2023, 1 mês tem 31 dias
const comparisonJan = Temporal.Duration.compare(oneMonth, thirtyDays, { relativeTo: startOfJanuary });
console.log(`1 mês vs 30 dias em Jan 2023: ${comparisonJan}`); // 1 (1 mês > 30 dias)
// Em fevereiro de 2024 (ano bissexto), 1 mês tem 29 dias
const comparisonFeb = Temporal.Duration.compare(oneMonth, thirtyDays, { relativeTo: endOfFebruaryLeap });
console.log(`1 mês vs 30 dias em Fev 2024: ${comparisonFeb}`); // -1 (1 mês < 30 dias)
Normalizando Durações com normalize() e round()
Durações podem ser representadas de muitas maneiras (ex: 90 minutos ou 1 hora e 30 minutos). A normalização e o arredondamento ajudam a padronizar essas representações para consistência e exibição.
normalize()
O método normalize() simplifica os componentes da duração quando possível (ex: 60 minutos se tornam 1 hora, 24 horas se tornam 1 dia, desde que o contexto `relativeTo` permita se meses/anos estiverem envolvidos).
const longMinutes = Temporal.Duration.from({ minutes: 90 });
console.log(longMinutes.toString()); // PT90M
console.log(longMinutes.normalize().toString()); // PT1H30M
const multipleDays = Temporal.Duration.from({ hours: 48 });
console.log(multipleDays.toString()); // PT48H
console.log(multipleDays.normalize().toString()); // P2D
round()
O método round() é mais poderoso para transformar e arredondar durações para unidades específicas. Ele recebe um objeto de opções com:
largestUnit: A maior unidade a ser incluída na saída (ex: 'years', 'days', 'hours').smallestUnit: A menor unidade a ser incluída na saída (ex: 'minutes', 'seconds', 'milliseconds').roundingIncrement: Um inteiro pelo qual arredondar a menor unidade (ex: 5 para arredondar para os 5 minutos mais próximos).roundingMode: Como lidar com empates (ex: 'halfExpand', 'trunc', 'ceil', 'floor').relativeTo: Necessário para arredondar durações contendo anos, meses ou semanas.
const complexDuration = Temporal.Duration.from({ hours: 2, minutes: 45, seconds: 30 });
// Arredondar para a hora mais próxima
const roundedToHours = complexDuration.round({ smallestUnit: 'hour' });
console.log(roundedToHours.toString()); // PT3H
// Arredondar para os 30 minutos mais próximos, mantendo as horas
const roundedTo30Minutes = complexDuration.round({
largestUnit: 'hour',
smallestUnit: 'minute',
roundingIncrement: 30
});
console.log(roundedTo30Minutes.toString()); // PT3H
const preciseDuration = Temporal.Duration.from({ minutes: 123, seconds: 45 });
// Exibir como horas e minutos
const formattedDuration = preciseDuration.round({ largestUnit: 'hour', smallestUnit: 'minute' });
console.log(formattedDuration.toString()); // PT2H4M
// Arredondar com meses/anos requer relativeTo
const longTermDuration = Temporal.Duration.from({ months: 1, days: 10 });
const referenceDate = Temporal.PlainDate.from('2023-01-15');
// Arredondar para meses, relativo a uma data
const roundedToMonths = longTermDuration.round({ largestUnit: 'month', smallestUnit: 'month', relativeTo: referenceDate });
console.log(roundedToMonths.toString()); // P1M
Calculando Durações Entre Objetos Temporal
Um dos usos mais frequentes de durações é calcular o intervalo de tempo entre dois pontos específicos no tempo. O Temporal fornece os métodos until() e since() em seus objetos de data e hora para este propósito.
Método until()
O método until() calcula a duração do objeto receptor para o objeto do argumento. Ele é inclusivo do ponto de início e exclusivo do ponto final. Ele recebe um objeto de opções semelhante a round() para especificar as unidades desejadas e o comportamento de arredondamento.
const startDate = Temporal.PlainDate.from('2023-01-01');
const endDate = Temporal.PlainDate.from('2023-03-15');
// Duração nas maiores unidades possíveis (meses, depois dias)
const projectLength = startDate.until(endDate);
console.log(projectLength.toString()); // P2M14D
// Duração puramente em dias
const totalDays = startDate.until(endDate, { largestUnit: 'day' });
console.log(totalDays.toString()); // P73D
// Duração entre dois tempos específicos, respeitando fusos horários
const meetingStart = Temporal.ZonedDateTime.from('2024-07-20T10:00:00[America/New_York]');
const meetingEnd = Temporal.ZonedDateTime.from('2024-07-20T11:30:00[America/New_York]');
const elapsedMeetingTime = meetingStart.until(meetingEnd, { largestUnit: 'hour', smallestUnit: 'minute' });
console.log(elapsedMeetingTime.toString()); // PT1H30M
// Duração entre fusos horários (de NYC para Londres)
const nyStartTime = Temporal.ZonedDateTime.from('2024-08-01T09:00:00[America/New_York]');
const londonEndTime = Temporal.ZonedDateTime.from('2024-08-01T17:00:00[Europe/London]');
const travelDuration = nyStartTime.until(londonEndTime);
console.log(travelDuration.toString()); // PT13H (Tempo real decorrido, não a diferença do relógio de parede)
O último exemplo é particularmente perspicaz. Embora Nova York esteja 5 horas atrás de Londres, e os horários de relógio de parede sejam 9h e 17h no mesmo dia, o método until() calcula corretamente o tempo real decorrido de 13 horas. Isso ocorre porque ZonedDateTime lida implicitamente com a diferença de fuso horário.
Método since()
O método since() é o inverso de until(). Ele calcula a duração do objeto do argumento para o objeto receptor, resultando em uma duração negativa se o argumento estiver no futuro em relação ao receptor.
const currentDateTime = Temporal.ZonedDateTime.from('2024-06-15T12:00:00[Europe/Paris]');
const historicEvent = Temporal.ZonedDateTime.from('2024-01-01T00:00:00[Europe/Paris]');
const timeSinceEvent = currentDateTime.since(historicEvent, { largestUnit: 'month', smallestUnit: 'day' });
console.log(timeSinceEvent.toString()); // P5M14D
const futureDate = Temporal.PlainDate.from('2025-01-01');
const pastDate = Temporal.PlainDate.from('2024-01-01');
const durationFromFuture = pastDate.since(futureDate);
console.log(durationFromFuture.toString()); // P-1Y
Lidando com Diferentes Unidades e Arredondamento para Durações Calculadas
Ao calcular durações, muitas vezes é necessário especificar `largestUnit` e `smallestUnit` para obter uma representação legível por humanos, especialmente para idade, tempo decorrido ou contagens regressivas.
const birthDate = Temporal.PlainDate.from('1990-07-15');
const today = Temporal.PlainDate.from('2024-06-15');
// Calcular idade em anos, meses e dias
const age = birthDate.until(today, { largestUnit: 'year', smallestUnit: 'day' });
console.log(`Idade: ${age.years} anos, ${age.months} meses, ${age.days} dias`); // Idade: 33 anos, 11 meses, 0 dias
// Calcular o tempo restante para uma tarefa em horas e minutos
const now = Temporal.Instant.fromEpochSeconds(Date.now() / 1000);
const deadline = Temporal.Instant.from('2024-07-01T09:00:00Z');
const timeLeft = now.until(deadline, { largestUnit: 'hour', smallestUnit: 'minute', roundingMode: 'ceil' });
console.log(`Tempo restante: ${timeLeft.hours} horas e ${timeLeft.minutes} minutos.`); // Exemplo: Tempo restante: 355 horas e 38 minutos.
Formatando Durações para Públicos Globais
Embora Temporal.Duration forneça representações programáticas e precisas de intervalos de tempo, ele não possui um método toLocaleString() embutido. Isso é intencional: durações são comprimentos de tempo abstratos, e sua exibição pode variar muito dependendo do contexto, localidade e nível de detalhe desejado. Você, como desenvolvedor, é responsável por apresentar as durações de uma maneira amigável e globalmente compreensível.
Representação em String ISO 8601
O método padrão toString() de um objeto Temporal.Duration retorna sua representação em string ISO 8601. Isso é excelente para comunicação máquina a máquina, serialização e armazenamento, mas raramente para exibição direta aos usuários finais.
const examDuration = Temporal.Duration.from({ hours: 2, minutes: 15 });
console.log(examDuration.toString()); // PT2H15M
const holidayDuration = Temporal.Duration.from({ weeks: 2, days: 3 });
console.log(holidayDuration.toString()); // P2W3D
Formatação Manual para Legibilidade e Internacionalização
Para exibição ao usuário, você normalmente extrairá os componentes de uma duração e os formatará usando interpolação de string e a API Intl do JavaScript.
Aqui está um exemplo de função personalizada que formata uma duração:
function formatDurationToHumanReadable(duration, locale = 'en-US') {
const parts = [];
// Usando Intl.NumberFormat para formatação de números ciente da localidade
const numberFormatter = new Intl.NumberFormat(locale);
// Lógica de pluralização simplificada para o exemplo em português
if (duration.years !== 0) {
parts.push(numberFormatter.format(duration.years) + ' ' + (duration.years === 1 ? 'ano' : 'anos'));
}
if (duration.months !== 0) {
parts.push(numberFormatter.format(duration.months) + ' ' + (duration.months === 1 ? 'mês' : 'meses'));
}
if (duration.weeks !== 0) {
parts.push(numberFormatter.format(duration.weeks) + ' ' + (duration.weeks === 1 ? 'semana' : 'semanas'));
}
if (duration.days !== 0) {
parts.push(numberFormatter.format(duration.days) + ' ' + (duration.days === 1 ? 'dia' : 'dias'));
}
if (duration.hours !== 0) {
parts.push(numberFormatter.format(duration.hours) + ' ' + (duration.hours === 1 ? 'hora' : 'horas'));
}
if (duration.minutes !== 0) {
parts.push(numberFormatter.format(duration.minutes) + ' ' + (duration.minutes === 1 ? 'minuto' : 'minutos'));
}
if (duration.seconds !== 0) {
const roundedSeconds = numberFormatter.format(duration.seconds.toFixed(0));
parts.push(roundedSeconds + ' ' + (duration.seconds === 1 ? 'segundo' : 'segundos'));
}
if (parts.length === 0) {
if (duration.milliseconds !== 0 || duration.microseconds !== 0 || duration.nanoseconds !== 0) {
const totalMs = duration.milliseconds + duration.microseconds / 1000 + duration.nanoseconds / 1_000_000;
return numberFormatter.format(totalMs.toFixed(2)) + ' milissegundos';
}
return '0 segundos';
}
// Junção de partes para português
if (parts.length > 1) {
const lastPart = parts.pop();
return parts.join(', ') + ' e ' + lastPart;
} else {
return parts[0];
}
}
const tripDuration = Temporal.Duration.from({ days: 3, hours: 10, minutes: 45 });
console.log(formatDurationToHumanReadable(tripDuration, 'pt-BR')); // 3 dias, 10 horas e 45 minutos
const meetingReminder = Temporal.Duration.from({ minutes: 5 });
console.log(formatDurationToHumanReadable(meetingReminder, 'pt-BR')); // 5 minutos
const microDuration = Temporal.Duration.from({ nanoseconds: 1234567 });
console.log(formatDurationToHumanReadable(microDuration, 'pt-BR')); // 1,23 milissegundos
Para pluralização mais avançada e formatação de listas localizadas, você pode combinar isso com Intl.RelativeTimeFormat ou bibliotecas de templates de string mais complexas. A chave é separar o cálculo da duração (tratado pelo Temporal) de sua apresentação (tratada por sua lógica de formatação e Intl).
Usando Intl.DurationFormat (Futuro/Proposta)
Há uma proposta TC39 em andamento para Intl.DurationFormat, que visa fornecer uma maneira nativa e ciente da localidade para formatar durações. Se padronizada e implementada, simplificaria muito a formatação manual mostrada acima, oferecendo uma solução robusta para internacionalização diretamente no ecossistema JavaScript.
Provavelmente funcionaria de forma semelhante a outros objetos Intl:
// Isto é hipotético e baseado no estado atual da proposta
// Verifique a compatibilidade do navegador antes de usar em produção
/*
const duration = Temporal.Duration.from({ days: 1, hours: 2, minutes: 30 });
const formatter = new Intl.DurationFormat('pt-BR', {
style: 'long',
years: 'long',
days: 'long',
hours: 'long',
minutes: 'long',
});
console.log(formatter.format(duration)); // "1 dia, 2 horas e 30 minutos"
const shortFormatter = new Intl.DurationFormat('pt-BR', { style: 'short', hours: 'numeric', minutes: 'numeric' });
console.log(shortFormatter.format(duration)); // "1 d, 2 h, 30 min"
*/
Embora ainda não esteja disponível em todos os ambientes, fique de olho nesta proposta, pois ela promete ser a solução definitiva para a formatação global de durações no futuro.
Casos de Uso Práticos e Considerações Globais
Temporal.Duration não é apenas uma melhoria acadêmica; ele resolve problemas do mundo real em aplicações que operam em diferentes fusos horários e culturas.
1. Agendamento e Gerenciamento de Eventos
Para plataformas que gerenciam eventos, conferências ou compromissos internacionais, calcular a duração dos eventos, o tempo até um evento ou a duração de um intervalo é crítico. Temporal.Duration garante que esses cálculos sejam precisos, independentemente da localização do usuário ou das regras de fuso horário local.
// Calcular o tempo restante para um webinar global
const now = Temporal.ZonedDateTime.from('2024-07-25T10:00:00[Europe/London]'); // Exemplo de hora atual
const webinarStart = Temporal.ZonedDateTime.from('2024-07-26T14:30:00[Asia/Tokyo]');
const timeUntilWebinar = now.until(webinarStart, {
largestUnit: 'hour',
smallestUnit: 'minute',
roundingMode: 'ceil' // Arredondar para cima para garantir que os usuários não percam
});
console.log(`O webinar começa em ${timeUntilWebinar.hours} horas e ${timeUntilWebinar.minutes} minutos.`);
// A saída será precisa com base no tempo real decorrido entre estes dois ZonedDateTimes.
2. Métricas de Desempenho e Logging
Medir o tempo de execução de operações, tempos de resposta de API ou durações de trabalhos em lote requer alta precisão. Temporal.Duration, especialmente quando combinado com Temporal.Instant (que oferece precisão de nanossegundo), é ideal para isso.
const startTime = Temporal.Instant.now();
// Simular uma operação complexa
for (let i = 0; i < 1_000_000; i++) { Math.sqrt(i); }
const endTime = Temporal.Instant.now();
const executionDuration = startTime.until(endTime);
// Formatar para segundos com milissegundos para exibição
const formattedExecution = executionDuration.round({ smallestUnit: 'millisecond', largestUnit: 'second' });
console.log(`A operação levou ${formattedExecution.seconds}.${String(formattedExecution.milliseconds).padStart(3, '0')} segundos.`);
3. Cálculos Financeiros
Em finanças, intervalos de tempo precisos são primordiais para calcular juros, prazos de empréstimos ou períodos de investimento. O número exato de dias, meses ou anos em um período precisa ser preciso, especialmente ao lidar com anos bissextos ou comprimentos específicos de meses.
const loanStartDate = Temporal.PlainDate.from('2023-04-01');
const loanEndDate = Temporal.PlainDate.from('2028-03-31');
const loanTerm = loanStartDate.until(loanEndDate, { largestUnit: 'year', smallestUnit: 'month' });
console.log(`Prazo do empréstimo: ${loanTerm.years} anos e ${loanTerm.months} meses.`); // 4 anos e 11 meses
4. Desafios de Internacionalização Revisitados
-
Fusos Horários e DST: O
Temporal.Durationem si é agnóstico de fuso horário; ele representa uma duração fixa de tempo. No entanto, quando você adiciona ou subtrai umaDuraçãode/para umZonedDateTime, o Temporal aplica corretamente as regras de fuso horário, incluindo as mudanças do Horário de Verão. Isso garante que uma "duração de 24 horas" realmente avance umZonedDateTimeem 24 horas reais, mesmo que isso signifique cruzar um limite de DST que torna o dia do *relógio de parede* mais curto ou mais longo. Essa consistência é uma grande vitória sobre o `Date`. -
Variações Culturais: Diferentes culturas expressam durações de maneiras diferentes. Embora
Temporal.Durationforneça os componentes brutos (anos, meses, dias, etc.), é sua responsabilidade usar APIsIntlou lógica personalizada para apresentá-los de uma forma que ressoe com a localidade do usuário. Por exemplo, algumas culturas podem expressar "1 hora e 30 minutos" como "90 minutos" ou usar regras de pluralização diferentes. -
Sistemas de Calendário: O Temporal também suporta diferentes sistemas de calendário (ex: calendários japonês, persa, islâmico). Embora a
Duraçãoem si seja agnóstica de calendário, quando interage com tipos cientes de calendário comoPlainDateouZonedDateTime, a aritmética respeita as regras específicas do calendário (ex: número de dias em um mês, regras de ano bissexto dentro daquele calendário). Isso é crucial para aplicações globais que podem precisar exibir datas em calendários não gregorianos.
Status Atual e Adoção do Temporal
No final de 2023/início de 2024, a API Temporal do JavaScript é uma proposta TC39 no Estágio 3. Isso significa que a especificação está amplamente estável e está passando por implementação e testes em vários motores e navegadores JavaScript. Grandes navegadores como Chrome, Firefox e Safari estão trabalhando ativamente na implementação do Temporal, com flags experimentais ou versões iniciais já disponíveis.
Embora ainda não esteja universalmente disponível sem um polyfill, seu estágio avançado indica que é muito provável que se torne uma parte padrão do JavaScript. Os desenvolvedores podem começar a experimentar o Temporal hoje usando polyfills ou habilitando recursos experimentais em seus ambientes de desenvolvimento de navegador. A adoção antecipada permite que você se adiante, entenda seus benefícios e o integre em seus projetos à medida que o suporte dos navegadores for lançado.
Sua eventual adoção generalizada reduzirá significativamente a necessidade de bibliotecas externas de data/hora, fornecendo uma solução nativa e robusta para o gerenciamento de tempo em JavaScript.
Melhores Práticas para Usar o Temporal Duration
Para maximizar os benefícios do Temporal.Duration em suas aplicações, considere estas melhores práticas:
-
Prefira
Temporal.Duration.from(): Ao criar durações a partir de componentes conhecidos ou strings ISO,Temporal.Duration.from()com um objeto literal é muitas vezes mais legível e menos propenso a erros do que os argumentos posicionais do construtor. -
Use
relativeTopara Comparações Ambíguas: Sempre forneça uma opçãorelativeToao comparar ou arredondar durações que contenham anos, meses ou semanas. Isso garante cálculos precisos, fornecendo o contexto de calendário necessário. -
Aproveite
until()esince(): Para calcular o intervalo entre dois pontos específicos no tempo, prefira os métodosuntil()esince()nos objetos de data e hora do Temporal. Eles lidam corretamente com as complexidades de fusos horários e DST. -
Normalize e Arredonde para Exibição: Antes de apresentar durações aos usuários, considere usar
normalize()e especialmenteround()para converter a duração nas unidades mais apropriadas e compreensíveis (ex: converter 90 minutos para "1 hora e 30 minutos"). -
Separe a Representação Interna da Exibição: Mantenha seus cálculos internos de duração precisos com
Temporal.Duration. Transforme e formate para exibição apenas ao renderizar a interface do usuário, usando funções de formatação personalizadas e a APIIntlpara precisão global. - Mantenha-se Atualizado: Fique de olho no progresso da API Temporal e na compatibilidade dos navegadores. À medida que ela avança para a padronização completa, o ecossistema evoluirá e novas ferramentas ou melhores práticas podem surgir.
- Esteja Ciente da Precisão: Embora o Temporal suporte precisão de nanossegundo, use apenas a precisão de que você realmente precisa. Uma precisão maior pode, às vezes, dificultar a depuração ou resultar em arredondamentos inesperados ao converter para exibições de menor precisão.
Conclusão
A introdução do Temporal.Duration marca um avanço significativo para os desenvolvedores JavaScript que lidam com a aritmética de intervalos de tempo. Ao fornecer uma API imutável, explícita e globalmente consciente, o Temporal aborda as limitações de longa data do antigo objeto Date.
Agora você pode realizar cálculos de tempo complexos com confiança, medir períodos com precisão e apresentar durações de uma maneira que seja precisa e culturalmente apropriada para usuários em todo o mundo. Seja calculando a duração de um ciclo de lançamento de software, o tempo restante até o lançamento de um produto global ou a idade exata de uma pessoa, o Temporal.Duration oferece as ferramentas de que você precisa para construir aplicações robustas e confiáveis.
Abrace o Temporal.Duration e transforme a maneira como você gerencia o tempo em seus projetos JavaScript. O futuro do tratamento de data e hora em JavaScript está aqui, prometendo uma experiência de desenvolvimento mais previsível, poderosa e globalmente compatível.